﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using System.IO;
using Newtonsoft.Json;

namespace Tsual.DataPipeline
{
    public abstract class AbstractDataPipeResult
    {
        public readonly DataPipeEnum dataPipeEnum;
        public Type PipeType { get; private set; }
        internal AbstractDataPipeResult SetPipeType(Type PipeType)
        {
            this.PipeType = PipeType;
            return this;
        }

        protected AbstractDataPipeResult(DataPipeEnum dataPipeEnum)
        {
            this.dataPipeEnum = dataPipeEnum;
        }
    }

    public sealed class DataPipeReadResult<T> : AbstractDataPipeResult
    {
        private DataPipeReadResult() : base(DataPipeEnum.Read) { }

        public T Data { get; private set; }
        public DataPipeReadResultEnum Result { get; private set; }
        public Exception Exception { get; private set; }

        public static DataPipeReadResult<T> Success(T data)
        {
            return new DataPipeReadResult<T>()
            {
                Data = data,
                Result = DataPipeReadResultEnum.Success
            };
        }

        public static DataPipeReadResult<T> Error(Exception exception = null)
        {
            return new DataPipeReadResult<T>()
            {
                Exception = exception,
                Result = DataPipeReadResultEnum.Error
            };
        }

        public static DataPipeReadResult<T> Notfound()
        {
            return new DataPipeReadResult<T>()
            {
                Result = DataPipeReadResultEnum.Notfound
            };
        }

        internal static DataPipeReadResult<T> Skip()
        {
            return new DataPipeReadResult<T>()
            {
                Result = DataPipeReadResultEnum.Skip
            };
        }
    }

    public sealed class DataPipeWriteResult : AbstractDataPipeResult
    {
        private DataPipeWriteResult() : base(DataPipeEnum.Write) { }

        public DataPipeWriteResultEnum Result { get; private set; }
        public Exception Exception { get; private set; }

        public static DataPipeWriteResult Success()
        {
            return new DataPipeWriteResult()
            {
                Result = DataPipeWriteResultEnum.Success
            };
        }

        public static DataPipeWriteResult Error(Exception exception = null)
        {
            return new DataPipeWriteResult()
            {
                Exception = exception,
                Result = DataPipeWriteResultEnum.Error
            };
        }

        internal static DataPipeWriteResult Skip()
        {
            return new DataPipeWriteResult()
            {
                Result = DataPipeWriteResultEnum.Skip
            };
        }
    }

    public sealed class DataPipelineExecutionResult<T, TMeta>
    {
        public DataPipelineExecutionResult(TMeta meta)
        {
            Meta = meta;
        }

        public ConcurrentQueue<AbstractDataPipeResult> PipeResults { get; } = new ConcurrentQueue<AbstractDataPipeResult>();
        public T Data { get; internal set; }
        public TMeta Meta { get; internal set; }
        public DateTime StartTime { get; } = DateTime.Now;
        public DateTime EndTime { get; internal set; }

        public DataPipelineExcutionStatus ReadStatus { get; internal set; }
        public DataPipelineExcutionStatus WriteStatus { get; internal set; }

        public List<Type> CleanTypes { get; internal set; } = new List<Type>();
    }

    public static class DataPipelineExecutionResultExtensions
    {
        public static T GetResultOrThrowException<T, TMeta>(this Task<DataPipelineExecutionResult<T, TMeta>> pipelineExecutionResult, int timeout = 0)
        {
            if (pipelineExecutionResult.Status == TaskStatus.Created)
                pipelineExecutionResult.Start();

            if (timeout < 1) pipelineExecutionResult.Wait();
            else pipelineExecutionResult.Wait(timeout);

            switch (pipelineExecutionResult.Status)
            {
                case TaskStatus.Faulted:
                    throw pipelineExecutionResult.Exception;
                case TaskStatus.RanToCompletion:
                    return pipelineExecutionResult.Result.GetResultOrThrowException();
                default:
                    throw new DataPipelineException("Task of datepipe haven't finished work yet");
            }
        }

        public static T GetResultOrThrowException<T, TMeta>(this DataPipelineExecutionResult<T, TMeta> pipelineExecutionResult)
        {
            if (pipelineExecutionResult.WriteStatus == DataPipelineExcutionStatus.Error)
                throw (pipelineExecutionResult.PipeResults.Last() as DataPipeWriteResult).Exception;
            else if (pipelineExecutionResult.ReadStatus == DataPipelineExcutionStatus.Error)
                throw (pipelineExecutionResult.PipeResults.Last() as DataPipeReadResult<T>).Exception;
            else return pipelineExecutionResult.Data;
        }

        public static void WriteResultToJsonFile<T, TMeta>(this DataPipelineExecutionResult<T, TMeta> pipelineExecutionResult, string fullPath)
        {
            if (string.IsNullOrWhiteSpace(fullPath))
            {
                throw new ArgumentException("fullPath cannot be empty", nameof(fullPath));
            }

            using (var fs = File.Open(fullPath, FileMode.Append))
            using (var sw = new StreamWriter(fs))
            {
                sw.Write(JsonConvert.SerializeObject(pipelineExecutionResult));
                sw.Flush();
            }
        }
    }
}
